package item

import (
	"base"
	"emma/errors"
	"game_server/logic/asset/api"
	"game_server/logic/asset/handler"
	"game_server/logic/db/event"
	"game_server/logic/db/mark"
	. "game_server/logic/internal"
	"game_server/logic/item/model"
	"game_server/model"
	"proto/base"
	"proto/item"
)

type itemChange map[base.AssetItemId]int64
type itemSystem struct {
}

var system = new(itemSystem)

func init() {
	Event.Listen(db_event.Save, system.onSave)
}

func init() {
	Skeleton.AfterInit(func() {
		asset_api.Register(system)
	})
}

func (*itemSystem) onSave(player *model.Player) {
	if len(player.Item.Inserted) > 0 {
		player.Item.Inserted = make(map[base.AssetItemId]bool)
	}

	if len(player.Item.Updated) > 0 {
		player.Item.Updated = make(map[base.AssetItemId]bool)
	}

	if len(player.Item.Deleted) > 0 {
		player.Item.Deleted = make(map[base.AssetItemId]bool)
	}
}

func (*itemSystem) AssetType() base.AssetType {
	return base.AssetType_Item
}

func (*itemSystem) GetAsset(player *model.Player, args *asset_handler.GetArgs) (result base.AssetItemCount, err error) {
	itemId := args.ItemId

	result = system.getCount(player, itemId)

	return
}

func (*itemSystem) HaveAsset(player *model.Player, args *asset_handler.HaveArgs) (err error) {
	itemId := args.ItemId
	itemCount := args.ItemCount

	_, err = system.haveAsset(player, itemId, itemCount)

	return
}

func (*itemSystem) AddAsset(player *model.Player, args *asset_handler.AddArgs) (err error) {
	itemId := args.ItemId
	itemCount := args.ItemCount

	var item *item_model.Item
	if item, err = system.addAsset(player, itemId, itemCount); err != nil {
		return
	}

	if _, exists := player.Item.Data[itemId]; exists {
		player.Item.Updated[itemId] = true
	} else {
		player.Item.Inserted[itemId] = true
		player.Item.Data[itemId] = item
	}

	change := system.getChange(args.Changes)
	change[itemId] += int64(itemCount)
	player.Item.OnChange(itemId, int64(itemCount))

	return
}

func (*itemSystem) addAsset(player *model.Player, itemId base.AssetItemId, itemCount base.AssetItemCount) (item *item_model.Item, err error) {
	data := player.Item.Data
	var exists bool
	if item, exists = data[itemId]; !exists {
		item = item_model.NewItem(player.Id(), itemId)
	}

	if base.AssetItemMaxCount-itemCount < item.Count {
		code := int(proto_base.ErrorCode_ItemOverflow)
		err = errors.New(code, "item %v overflow, have %v, new addItem %v", itemId, item.Count, itemCount)
		return
	}

	item.Count += itemCount

	return
}

func (*itemSystem) RemoveAsset(player *model.Player, args *asset_handler.RemoveArgs) (err error) {
	itemId := args.ItemId
	itemCount := args.ItemCount

	var item *item_model.Item
	if item, err = system.removeAsset(player, itemId, itemCount); err != nil {
		return
	}

	if item.Count < 1 {
		player.Item.Deleted[itemId] = true
		delete(player.Item.Data, itemId)
	} else {
		player.Item.Updated[itemId] = true
	}

	change := system.getChange(args.Changes)
	change[itemId] -= int64(itemCount)
	player.Item.OnChange(itemId, -int64(itemCount))

	return
}

func (*itemSystem) removeAsset(player *model.Player, itemId base.AssetItemId, itemCount base.AssetItemCount) (item *item_model.Item, err error) {
	if item, err = system.haveAsset(player, itemId, itemCount); err != nil {
		return
	}

	item.Count -= itemCount

	return
}

func (*itemSystem) haveAsset(player *model.Player, itemId base.AssetItemId, itemCount base.AssetItemCount) (item *item_model.Item, err error) {
	var curCount base.AssetItemCount
	data := player.Item.Data
	var exists bool
	item, exists = data[itemId]
	if exists {
		curCount = item.Count
	}

	if curCount < itemCount {
		code := int(proto_base.ErrorCode_ItemNotEnough)
		err = errors.New(code, "item %v not enough, have %v, need %v", itemId, curCount, itemCount)
		return
	}

	return
}

func (*itemSystem) getChange(changes asset_handler.Changes) (result itemChange) {
	if value, exists := changes[system]; exists {
		result = value.(itemChange)
	} else {
		result = make(itemChange)
		changes[system] = result
	}

	return
}

func (*itemSystem) RollbackAsset(player *model.Player, args *asset_handler.RollbackArgs) {
	var change itemChange
	if value, exists := args.Changes[system]; exists {
		change = value.(itemChange)
	} else {
		return
	}

	for itemId, changeCount := range change {
		if changeCount == 0 {
			continue
		}

		player.Item.OnChange(itemId, -changeCount)
		if changeCount > 0 {
			itemCount := base.AssetItemCount(changeCount)
			item, err := system.removeAsset(player, itemId, itemCount)
			if err == nil {
				if item.Count < 1 {
					delete(player.Item.Inserted, itemId)
				} else {
					delete(player.Item.Updated, itemId)
				}
			}
		} else if changeCount < 0 {
			itemCount := base.AssetItemCount(-changeCount)
			item, err := system.addAsset(player, itemId, itemCount)
			if err == nil && item.Count == itemCount {
				delete(player.Item.Deleted, itemId)
			}
		}
	}
}

func (*itemSystem) OnAssetChanged(player *model.Player, args *asset_handler.ChangeArgs) {
	Event.Notify(db_event.Collect, player, db_mark.Item)

	player.SetSyncMark(system, true)
	player.DelayCall(func() {
		if !player.HasSyncMark(system) {
			return
		}
		player.SetSyncMark(system, false)
		system.syncChange(player)
	})
}

func (*itemSystem) syncChange(player *model.Player) {
	if !player.Online() {
		return
	}

	data := player.Item.Data
	response := new(proto_item.SyncChange)
	response.Table = make(map[int32]uint32)
	for itemId, changeCount := range player.Item.ResetChanges() {
		if changeCount == 0 {
			continue
		}

		var itemCount base.AssetItemCount
		if item, exists := data[itemId]; exists {
			itemCount = item.Count
		}
		response.Table[itemId] = itemCount
	}

	if len(response.Table) < 1 {
		return
	}

	rpc.SyncChange(player, response)
}

func (*itemSystem) getCount(player *model.Player, itemId base.AssetItemId) (result base.AssetItemCount) {
	data := player.Item.Data
	if item, exists := data[itemId]; exists {
		result = item.Count
	}

	return
}
